<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta name="description" content="前言在RFC3986规范中，URL地址的匹配方式如下： foo:(scheme部分)&#x2F;&#x2F;cnodejs.org:3000(authority部分)&#x2F;over&#x2F;there(path部分)?name&#x3D;ferret(query部分)#nose(fragment部分) 在Node中有一个模块parseurl专门用于解析URL地址。 HTTP采用请求、响应模型：浏览器或其他客户端发起请求，服务端给予响应。首">
<meta property="og:type" content="article">
<meta property="og:title" content="Koa练习">
<meta property="og:url" content="http://example.com/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:description" content="前言在RFC3986规范中，URL地址的匹配方式如下： foo:(scheme部分)&#x2F;&#x2F;cnodejs.org:3000(authority部分)&#x2F;over&#x2F;there(path部分)?name&#x3D;ferret(query部分)#nose(fragment部分) 在Node中有一个模块parseurl专门用于解析URL地址。 HTTP采用请求、响应模型：浏览器或其他客户端发起请求，服务端给予响应。首">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2021-10-22T04:18:39.000Z">
<meta property="article:modified_time" content="2021-11-03T08:19:59.482Z">
<meta property="article:author" content="Li Yudong">
<meta property="article:tag" content="Koa框架">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>Koa练习 | Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          Koa练习
        </h1>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-22 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-22T12:18:39+08:00">2021-10-22</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:19:59" itemprop="dateModified" datetime="2021-11-03T16:19:59+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
        <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在RFC3986规范中，URL地址的匹配方式如下：</p>
<p>foo:(scheme部分)//cnodejs.org:3000(authority部分)/over/there(path部分)?name=ferret(query部分)#nose(fragment部分)</p>
<p>在Node中有一个模块parseurl专门用于解析URL地址。</p>
<p>HTTP采用请求、响应模型：浏览器或其他客户端发起请求，服务端给予响应。首先传输的就是HTTP头部信息，而头部信息通常被分为四部分：general header、request header、response header、entity header。对于开发者而言，无论是使用，还是性能调优，都必须掌握头部信息相关的内容。因为浏览器是依靠请求和响应中的头部信息来控制缓存的。</p>
<p>而Koa对HTTP头的处理分为：1、设置：ctx.header中无参数，则可用来获取所有的头部信息；2、获取：ctx.get用来获取特定的头部信息。</p>
<p>HTTP动词：基于RESTful架构，HTTP动词一共有5个，包括：GET、POST、PUT、PATCH、DELETE。在koa中可以使用ctx.method来获取HTTP动词。</p>
<p>HTTP状态码：是响应返回给浏览器的状态，客户端可以根据状态码及Content-Type值来进行渲染，网络请求部分会显示对应的状态码。</p>
<p>ctx.status = ctx.response.status;在Koa中，可以采用以下方式获取状态码并对其赋值。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"></span><br><span class="line">app.use(<span class="keyword">async</span> ctx =&gt; &#123;</span><br><span class="line">    ctx.status = <span class="number">404</span>;</span><br><span class="line">    ctx.type = <span class="string">"html"</span>;</span><br><span class="line">    ctx.body = <span class="string">"&lt;h1&gt;this is status demo&lt;/h1&gt;"</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">上述代码作用：1、将状态码赋值为404；2、设置Content-Type的返回值类型为HTML类型；</span></span><br><span class="line"><span class="comment">3、设置具体的返回body为一段AHTML代码。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>Cookie是在HTTP下，服务端或脚本维护客户工作站上存在的一种信息形式，是由Web服务器保存在客户端上的小文本文件。</p>
<p>Cookie的处理流程如下：服务端向客户端发送Cookie，客户端的浏览器把Cookie保存起来，然后每次请求浏览器时将Cookie发送到服务端。在HTML文档被发送之前，Web服务器会通过通过传送HTTP包头的Set-Cookie信息把一个Cookie发送到用户的浏览器中。</p>
<p>在Node.js中，Cookie是通过response.writeHead被写入的。</p>
<h1 id="请求解析"><a href="#请求解析" class="headerlink" title="请求解析"></a>请求解析</h1><h2 id="获取参数"><a href="#获取参数" class="headerlink" title="获取参数"></a>获取参数</h2><p>1、获取具名参数：带冒号的占位参数是具名参数，一般常用于RestfulAPI中。koa的插件koa-router中内置了ctx.params，使用它可以轻松获取具名参数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">'/:id'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = <span class="string">'show user id = '</span> + ctx.params.id</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>2、解析请求体：在Node.js原生的http模块中，请求体要基于流的方式接收与解析。在koa中，解析请求体的中间件是koa-bodyparser。用法十分简单，示例代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bodyparser = <span class="built_in">require</span>(<span class="string">'koa-bodyparser'</span>)();</span><br><span class="line">app.use(bodyparser);</span><br><span class="line"><span class="comment">//上述代码在全局中间件中引入koa-bodyparser</span></span><br><span class="line"><span class="comment">//用法的示例代码如下：</span></span><br><span class="line">router.post(<span class="string">'/post'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = ctx.request.body;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>3、获取查询字符串：一般前端讲的query是querystring的简写，指的是查询字符串。在koa中，可以通过ctx.quert来获取查询字符串对象。</p>
<h2 id="body解析"><a href="#body解析" class="headerlink" title="body解析"></a>body解析</h2><p>处理请求体的模块非常多，这里我们以常用的koa-bodyparser为例，列出和它有依赖关系的模块。</p>
<p>原理：在HTTP的请求头中，有些动词会带请求体，我们这是需要根据不同的Content-Type来返回body。Koa通过ctx.body向浏览器写入响应，而ctx上用了body关键字，所以只能通过ctx.request.body来处理请求体。</p>
<p>koa-bodyparser中的默认配置是启动JSON支持，所以只要在请求前添加koa-bodyparser这个中间件就可以。</p>
<p>请求表单是工作中最常用到的，以POST为例，它通常会用于进行3种处理：1、通用表单上传；2、普通表单上传、3、文件上传。</p>
<p>1、通用表单指的是form-data，主要依赖koa-bodyparser和koa-multer。</p>
<p>koa-bodyparser默认支持form类型；而koa-multer是一个非常便于使用的文件上传模块，使用koa-multer可以解析body中的流，并将其保存为文件；koa-multer不会处理multipart/form-data以外的任何表单。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'koa-multer'</span>);</span><br><span class="line"><span class="keyword">const</span> upload = multer(&#123;<span class="attr">dest</span>: <span class="string">'uploads/'</span>&#125;);</span><br><span class="line"><span class="comment">//koa-multer的使用非常简单，只需要把upload内置的中间件挂载到路由上即可，这里可以使用 upload.any()方法</span></span><br><span class="line">router.post(<span class="string">'/post/formdata'</span>,upload.any(), (ctx) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(ctx.req.files);</span><br><span class="line">    ctx.body = &#123;</span><br><span class="line">        status: &#123;</span><br><span class="line">            code: <span class="number">0</span>,</span><br><span class="line">            msg: <span class="string">'upload sucess'</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        data: &#123;</span><br><span class="line">            body: ctx.req.body,</span><br><span class="line">            files: ctx.req.files</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>2、普通表单对应的Content-Type是x-www-form-urlencoded。这样，直接用ctx.body = ctx.request.body即可进行解析。</p>
<p>3、文件上传：Koa中koa-multer中间件使用示例如下：</p>
<p>1、需要将multer的上传目录配置为根目录下的uploads目录；2、upload变量上的函数有array、singgle、fields等，这些函数均可用于处理文件上传；3、multer的原始做法是通过ctx.req.files来获取上传的文件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)();</span><br><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'koa-multer'</span>);</span><br><span class="line"><span class="keyword">const</span> upload = multer(&#123;<span class="attr">dest</span>: <span class="string">'uploads/'</span>&#125;);</span><br><span class="line"></span><br><span class="line">router.prefix(<span class="string">'/upload'</span>);</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">'/'</span>,upload.any(), (ctx) =&gt; &#123;</span><br><span class="line">    ctx.body = &#123;</span><br><span class="line">        status: &#123;</span><br><span class="line">            code: <span class="number">0</span>,</span><br><span class="line">            msg: <span class="string">'upload sucess'</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        data: &#123;</span><br><span class="line">            body: ctx.req.files</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="built_in">module</span>.exports = router;</span><br></pre></td></tr></table></figure>

<p>文本解析：koa-bodyparser默认只支持form和JSON两种格式的解析，当出现Content-Type:”text/plain”的时候是无法处理的，需要开启text支持。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.use(bodyparser(&#123;</span><br><span class="line">    enableTypes:[<span class="string">'json'</span>, <span class="string">'form'</span>, <span class="string">'text'</span>]</span><br><span class="line">&#125;));</span><br><span class="line"><span class="comment">//接着我们在routes/user.js里写一个/post/raw路由，其用法和普通表单解析请求体的用法一致，通过ctx.request.body来访问请求体</span></span><br><span class="line">router.post(<span class="string">'/post/raw'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = ctx.request.body;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="HTTP模块"><a href="#HTTP模块" class="headerlink" title="HTTP模块"></a>HTTP模块</h2><p>Node.js的SDK提供了对HTTP客户端请求的支持，通用的做法是使用http.request函数，它的底层是http.ClientRequest，即客户端请求。</p>
<p>其实有5个通用的HTTP请求模块，借用这些模块，可以简化请求处理流程。</p>
<p>request模块：是一个Node.js模块，为HTTP客户端，可用于实现HTTP响应流的转接、模拟表单提交、支持HTTP认证、实现OAuth登录、自定义请求头等。</p>
<p>superagent是一个轻量级的Node.js模块，它可读性好，且在所有的Node.js应用中，superagent的API设计最为人性化，其中有大量可用插件。</p>
<p>superagent也是著名的测试模块supertest的基础模块，值得学习。</p>
<p>got是一个更为简单易用的模块，不具备较强的通用性，但胜在小巧。</p>
<p>node-fetch模块：Fetch是XMLHttpRequest的替代技术。Fetch API提供了一个window.fetch()方法，我们可以用它来发起对远程资源的请求，该方法返回的是一个Promise对象，可对返回的结果进行检索。</p>
<p>axios：是一个基于Promise的HTTP模块，可以被同时用在浏览器和node中。</p>
<h1 id="API与中间件"><a href="#API与中间件" class="headerlink" title="API与中间件"></a>API与中间件</h1><p>API是纯I/O应用，这是Node.js最擅长的场景，如果API是从异构服务中获得并组装的，那么API组装部分使用Node来编写是非常合适的。前面说过的BFF和APIProxy完全可以体现。</p>
<p>koa.res.api是一个Koa中间件，要将它挂载到app对象上，该中间件支持多种API调用方式。 </p>
<p>1、直接返回API接口；2、返回带有状态的JSON数据；3、返回JSON API；4、异常情况下返回错误结果。</p>
<p>响应处理：API的定义是简单的，但对于响应结果的处理却很麻烦，由于JS为动态语言，因此会经常抛出Uncaught TypeError类的异常。</p>
<p>除了前端请求需要做响应处理，使用Node进行API聚合也需要进行响应处理，这是Node开发中相当重要的内容。比较常见的是使用Lodash的_.get方法，根据对象路径获取值；如果获取的值是undefined，则会赋予解析结果以默认值，有效减少异常处理。</p>
<p>另一种更好得方式是使用TypeScript，其作为一个静态语言，提前了类型检查的时机。在TypeScript里，接口的作用是为这些类型及为代码定义契约。根据接口信息，我们可以对值所具有的结构进行类型检查。</p>
<p>RESRTful API：RESRTful API是以HTTP为依托，还原了URL的本质，就是说一个URL就是一个资源，用于表示状态的变化，不包含其他任何动作。</p>
<p>API访问鉴权：传统的Web站点可以通过登录来结局用户鉴权问题，而API接口的安全问题可以由以下原则确保：1、有调用者身份；2、请求具有唯一性；3、请求的参数不能被篡改；4、请求有效事件，即API对应的令牌的有效期要长一些。</p>
<p>目前流行的鉴权方式主要有：JSON WebToken、OAuth。</p>
<h2 id="常用中间件"><a href="#常用中间件" class="headerlink" title="常用中间件"></a>常用中间件</h2><h3 id="会话"><a href="#会话" class="headerlink" title="会话"></a>会话</h3><p>会话是最常见的，用来保持客户端和服务器端的状态；HTTP本身是无状态协议，但有时我们需要记录用户的状态。会话是一种记录客户状态的机制，和Cookie类似，但Cookie是保存在客户端中的，会话保存在服务器中。</p>
<p>Koa中有许多与会话相关的模块：koa-session，是基于Cookie的简单会话实现；koa-generic-session：Session Store的抽象层，目标是让会话能够存储在Redis或MongoDB等自定义持久化存储中。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> session = <span class="built_in">require</span>(<span class="string">'koa-generic-session'</span>);</span><br><span class="line"><span class="keyword">const</span> RedisStore = <span class="built_in">require</span>(<span class="string">'koa-redis'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line">app.key = [<span class="string">'keys'</span>, <span class="string">'keyskeys'</span>]</span><br><span class="line"><span class="comment">//加入全局中间件</span></span><br><span class="line">app.use(session(&#123;</span><br><span class="line">    store: <span class="keyword">new</span> RedisStore(),</span><br><span class="line">    ttl: <span class="number">30</span> * <span class="number">60</span> * <span class="number">1000</span></span><br><span class="line">&#125;))</span><br><span class="line"><span class="comment">//在路由中可以直接通过ctx.session对后面的中间件进行操作</span></span><br><span class="line">app.use(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> (ctx.path) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">'/get'</span>:</span><br><span class="line">            ctx.session.user = &#123; <span class="attr">name</span>: <span class="string">'i5ting'</span>&#125;</span><br><span class="line">            ctx.body = ctx.session.user</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">case</span> <span class="string">'/remove'</span>:</span><br><span class="line">            ctx.session = <span class="literal">null</span></span><br><span class="line">            ctx.body = <span class="string">"removed"</span></span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8080</span>);</span><br><span class="line"><span class="comment">/*以上代码的三个要点：</span></span><br><span class="line"><span class="comment">1、依赖Redis，因此需要先启动Redis服务器；</span></span><br><span class="line"><span class="comment">2、通过ctx.session进行会话信息处理；</span></span><br><span class="line"><span class="comment">3、TTL是Session Store的超时事件，</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h3 id="ETag"><a href="#ETag" class="headerlink" title="ETag"></a>ETag</h3><p>ETag是前端缓存优化的重要部分，ETag在服务端生成后，客户端通过If-Match或If-None-Match条件判断请求，来验证资源是否被修改。</p>
<p>在Koa里，需要使用koa-conditional-get和koa-etag插件来提供ETag功能。</p>
<h3 id="验证码"><a href="#验证码" class="headerlink" title="验证码"></a>验证码</h3><p>OTP：动态口令，根据专门的算法每隔60秒生成的一个与事件相关的、不可预测的随机数字组合。</p>
<h3 id="限制访问频率"><a href="#限制访问频率" class="headerlink" title="限制访问频率"></a>限制访问频率</h3><p>限制访问频率是对抗暴力破解的有效手段，最简单也最好用的办法是利用Redis的expire命令。</p>

    </div>
    
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束<i class="fa fa-paw"></i>感谢您的阅读-------------</div>
    
</div>

      
    </div>
        <div class="reward-container">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button onclick="var qr = document.getElementById('qr'); qr.style.display = (qr.style.display === 'none') ? 'block' : 'none';">
    打赏
  </button>
  <div id="qr" style="display: none;">
      
      <div style="display: inline-block;">
        <img src="/images/alipay.png" alt="Li Yudong 支付宝">
        <p>支付宝</p>
      </div>

  </div>
</div>

        

<div>
<ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者： </strong>Li Yudong
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://example.com/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/" title="Koa练习">http://example.com/2021/10/22/node/Koa练习/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" rel="noopener" target="_blank"><i class="fab fa-fw fa-creative-commons"></i>BY-NC-SA</a> 许可协议。转载请注明出处！
  </li>
</ul>
</div>

    
      <footer class="post-footer">
          <div class="post-tags">
              <a href="/tags/Koa%E6%A1%86%E6%9E%B6/" rel="tag"># Koa框架</a>
          </div>
    
        

    
        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2021/10/21/node/node%E4%B8%AD%E7%9A%84HTTP/" rel="prev" title="node中的HTTP">
      <i class="fa fa-chevron-left"></i> node中的HTTP
    </a></div>
      <div class="post-nav-item">
    <a href="/2021/10/24/node/%E5%89%8D%E7%AB%AF%E8%A7%86%E5%9B%BE%E6%A8%A1%E6%9D%BF/" rel="next" title="前端视图模板">
      前端视图模板 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#前言"><span class="nav-number">1.</span> <span class="nav-text">前言</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#请求解析"><span class="nav-number">2.</span> <span class="nav-text">请求解析</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#获取参数"><span class="nav-number">2.1.</span> <span class="nav-text">获取参数</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#body解析"><span class="nav-number">2.2.</span> <span class="nav-text">body解析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#HTTP模块"><span class="nav-number">2.3.</span> <span class="nav-text">HTTP模块</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#API与中间件"><span class="nav-number">3.</span> <span class="nav-text">API与中间件</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#常用中间件"><span class="nav-number">3.1.</span> <span class="nav-text">常用中间件</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#会话"><span class="nav-number">3.1.1.</span> <span class="nav-text">会话</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ETag"><span class="nav-number">3.1.2.</span> <span class="nav-text">ETag</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#验证码"><span class="nav-number">3.1.3.</span> <span class="nav-text">验证码</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#限制访问频率"><span class="nav-number">3.1.4.</span> <span class="nav-text">限制访问频率</span></a></li></ol></li></ol></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

